package com.lwl.office.dcconverter.converter;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.HexUtil;
import com.lwl.office.dcconverter.cons.DCDocumentEnum;
import com.lwl.office.dcconverter.cons.DCParagraphType;
import com.lwl.office.dcconverter.cons.DCTableCellValignEnum;
import com.lwl.office.dcconverter.element.*;
import com.lwl.office.dcconverter.style.DCParagraphStyle;
import com.lwl.office.dcconverter.style.DCRunStyle;
import com.lwl.office.dcconverter.style.DCTableCellStyle;
import com.lwl.office.dcconverter.util.ImgUtil;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.drawingml.x2006.main.CTGraphicalObject;
import org.openxmlformats.schemas.drawingml.x2006.main.impl.CTGraphicalObjectDataImpl;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTAnchor;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;
import org.openxmlformats.schemas.officeDocument.x2006.sharedTypes.STVerticalAlignRun;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * word转换器
 */
public class DCWordConverter extends Converter {
    private final static DCDocumentEnum documentType = DCDocumentEnum.WORD;
    protected DCRoot parse() {
        DCRoot root = new DCRoot();
        XWPFDocument document = (XWPFDocument) getInput();
        List<IBodyElement> bodyElements = document.getBodyElements();
        //记录段落解析过程中的上一段落，用于将连续的有序列表或无序列表进行串联
        AbsDCElement preParagraph=null;
        for (IBodyElement bodyElement : bodyElements) {
            if(bodyElement instanceof XWPFParagraph){
                XWPFParagraph paragraph = (XWPFParagraph) bodyElement;
                AbsDCElement curr = parseParagraph(paragraph,preParagraph);
                //如果上一个段落和当前的段落是同一个段落，则不再添加到root中，该情况出现再列表解析过程中
                if( preParagraph==null || preParagraph != curr){
                    preParagraph = curr;
                    root.addChild(preParagraph);
                }
            }else if(bodyElement instanceof XWPFTable){
                XWPFTable table = (XWPFTable) bodyElement;
                root.addChild(parseTable(table));
            }else if(bodyElement instanceof XWPFSDT){
                //TODO
            }
        }
        return root;
    }
    @Override
    protected Object out(DCDocumentEnum type) {
        if(type == DCDocumentEnum.HTML){
            return getRoot().toHtml();
        }
        return null;
    }

    /**
     * 初始化DCTable
     * @param table
     * @return
     */
    private DCTable initDCTable(XWPFTable table){
        int rowNum = table.getNumberOfRows();
        XWPFTableRow row = table.getRow(0);
        int colNum = 0;
        List<XWPFTableCell> tableCells = row.getTableCells();
        //统计表格列数
        for (XWPFTableCell tableCell : tableCells) {
            CTTc ctTc = tableCell.getCTTc();
            //获取单元格占的列数
            if(ctTc.isSetTcPr() && ctTc.getTcPr().isSetGridSpan()){
                CTDecimalNumber gridSpan = ctTc.getTcPr().getGridSpan();
                colNum += gridSpan.getVal().intValue();
            }else{
                colNum += 1;
            }
        }
        return new DCTable(rowNum,colNum);
    }

    /**
     * 解析表格[该方法代码略显潦草，暂时先这样，后续重构]
     * @param table
     * @return
     */
    private DCTable parseTable(XWPFTable table) {
        //将table初步映射到DCTable上
        DCTable dct = initDCTable(table);
        List<XWPFTableRow> rows = table.getRows();
        int flag=1;
        int[][] mergeMap = dct.getMergeMap();
        for (int i = 0; i < rows.size(); i++) {
            XWPFTableRow row = rows.get(i);
            DCTableRow dctr = dct.addRow();
            List<XWPFTableCell> cells = row.getTableCells();
            //j标记table中cell的位置，k标记dct.getMergeMap()的横坐标位置
            for (int j = 0,k=0; j < cells.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                DCTableCell dctc = dctr.addCell();
                DCTableCellStyle dctcStyle = new DCTableCellStyle(documentType);
                dctc.setStyle(dctcStyle);
                //标记dctc是否从dctr中移除了，移除操作发生在当dctc所在单元格被上一行的单元格所合并的情况下
                boolean dctcRemoved = false;
                //处理单元格样式
                CTTc ctTc = cell.getCTTc();
                if(ctTc.isSetTcPr()){
                    CTTcPr tcPr = ctTc.getTcPr();
                    //单元格垂直排列
                    if(tcPr.isSetVAlign()){
                        STVerticalJc.Enum align = tcPr.getVAlign().getVal();
                        dctcStyle.setValign(align==STVerticalJc.CENTER? DCTableCellValignEnum.CENTER:DCTableCellValignEnum.BOTTOM);
                    }
                    //跨行合并
                    if(tcPr.isSetVMerge()){
                        STMerge.Enum val = tcPr.getVMerge().getVal();
                        //合并的第一行，标识位重新开始
                        if(val == STMerge.RESTART){
                            mergeMap[i][k] = flag++;
                        }
                        //合并的其他行,标识位与上一行相同
                        else{
                            mergeMap[i][k] = mergeMap[i-1][k];
                            //把刚才加到dctr中的cell移除，因为该位置的cell被合并到上一行的cell中了
                            dctr.getChildren().remove(dctc);
                            dctcRemoved=true;
                        }
                    }else{
                        mergeMap[i][k] = flag++;
                    }
                    //处理完跨行后，不管有没有跨行，[i][k]位置已经设值，所以k++;
                    k++;
                    //合并列
                    if(tcPr.isSetGridSpan()){
                        int gridSpan = tcPr.getGridSpan().getVal().intValue();
                        int[] colMergeBit = mergeMap[i];
                        //合并列超过一列，则后面的标识位与前面的相同,同时k++
                        for(int t=1;t<gridSpan;t++,k++){
                            colMergeBit[k] = colMergeBit[k-1];
                        }
                    }
                }else{
                    mergeMap[i][k++] = flag++;
                }

                //处理段落
                if(dctcRemoved){
                    continue;
                }
                //设置单元格的flag
                dctc.setFlag(mergeMap[i][k-1]);
                List<XWPFParagraph> paragraphs = cell.getParagraphs();
                for (XWPFParagraph paragraph : paragraphs) {
                    DCParagraph dcp = dctc.addParagraph();
                    parseParagraph(paragraph,dcp);
                }
            }
        }
        //处理合并单元格信息
        if(dct.hasMerge()){
            List<DCTableCell> dctcs = dct.getCell();
            for (DCTableCell dctc : dctcs) {
                //根据mergerMap来设置单元格的rowspan和colspan属性
                //y用于记录flag出现的第一列下标
                int rowspan=1,colspan=0,y=0;
                for(int i=0;i<dct.getRowNum();i++){
                    //还未扫描到该flag出现的第一行位置，则挨个看是否在该行，colspan不为0，则表示行上已扫描到，则不需要扫描统计了
                    if(colspan == 0){
                        for(int j=0;j<dct.getColNum();j++){
                            //在合并位图中找到该标识位出现的第一个位置
                            if(mergeMap[i][j] == dctc.getFlag()){
                                //当行统计
                                colspan++;
                                y=j;
                            }else if(colspan > 0){
                                break;
                            }
                        }
                    }else{
                        //如果当前行的flag与上一行的flag相同，那么rowSpan++,否则标识当前flag的合并范围已经扫码完成，退出当前flag的扫描
                        if(mergeMap[i][y] == dctc.getFlag()){
                            rowspan++;
                        }else{
                            break;
                        }
                    }
                }
                dctc.setRowspan(rowspan);
                dctc.setColspan(colspan);
            }
        }
        return dct;
    }


    /**
     * 解析普通段落的公共方法
     * @param paragraph
     * @return
     */
    private DCParagraph parseParagraph(XWPFParagraph paragraph,AbsDCElement preParagraph) {
        DCParagraphType type = getType(paragraph);
        switch (type){
            case HEADER:
                return parseParagraph(paragraph,new DCHeader(getHeaderLvl(paragraph)));
            case ORDER_LIST:
                return parseOL(paragraph,preParagraph);
            case UNORDER_LIST:
                return parseUL(paragraph, preParagraph);
            case NORMAL:
                return parseParagraph(paragraph,new DCParagraph());
        }
        return null;
    }

    /**
     * 获取标题级别
     * @param paragraph
     * @return
     */
    private int getHeaderLvl(XWPFParagraph paragraph){
        CTP ctp = paragraph.getCTP();
        XWPFDocument document = paragraph.getDocument();
        if(ctp.isSetPPr()) {
            CTPPr pPr = ctp.getPPr();
            if (pPr.isSetPStyle()) {
                XWPFStyle style = document.getStyles().getStyle(pPr.getPStyle().getVal());
                CTStyle ctStyle = style.getCTStyle();
                if (style.getType() == STStyleType.PARAGRAPH && ctStyle.isSetPPr() && ctStyle.getPPr().isSetOutlineLvl()) {
                    return ctStyle.getPPr().getOutlineLvl().getVal().intValue()+1;
                }
            }
        }
        return 0;
    }

    /**
     * 解析ul
     * @param paragraph
     * @return
     */
    private DCUnorderedList parseUL(XWPFParagraph paragraph,AbsDCElement preParagraph){
        DCUnorderedList ul = (preParagraph.getClass() == DCUnorderedList.class)? (DCUnorderedList) preParagraph :new DCUnorderedList();
        DCList dcList = ul.addList();
        DCParagraph dcp = new DCParagraph();
        dcList.addChild(dcp);
        parseParagraph(paragraph,dcp);
        return ul;
    }
    /**
     * 解析ul
     * @param paragraph
     * @return
     */
    private DCOrderedList parseOL(XWPFParagraph paragraph,AbsDCElement preParagraph){
        DCOrderedList ol = (preParagraph.getClass() == DCOrderedList.class)? (DCOrderedList) preParagraph :new DCOrderedList();
        DCList dcList = ol.addList();
        DCParagraph dcp = new DCParagraph();
        dcList.addChild(dcp);
        parseParagraph(paragraph,dcp);
        return ol;
    }


    /**
     * 解析普通段落的公共方法
     * @param paragraph
     * @return
     */
    private <T extends DCParagraph> T parseParagraph(XWPFParagraph paragraph,T dcp) {
        //样式解析
        DCParagraphStyle dcps = new DCParagraphStyle(documentType);
        dcp.setStyle(dcps);
        CTP ctp = paragraph.getCTP();
        if(ctp.isSetPPr()){
            CTPPr pPr = ctp.getPPr();
            if(pPr.isSetSpacing()){
                CTSpacing spacing = pPr.getSpacing();
                Object before = spacing.getBefore();
                dcps.setSpacingBefore(spacing.getBefore() == null?null:before.toString());
                Object after = spacing.getAfter();
                dcps.setSpacingAfter(after==null?null:after.toString());
                Object line = spacing.getLine();
                dcps.setLineHeight(line==null?null:line.toString());
            }
            if(pPr.isSetInd()){
                CTInd ind = pPr.getInd();
                dcps.setInd(ind.isSetLeftChars()?ind.getLeftChars().toString():null);
            }
            if(pPr.isSetJc()){
                CTJc jc = pPr.getJc();
                dcps.setAlign(jc.getVal().toString());
            }
        }
        //子节点解析
        List<XWPFRun> runs = paragraph.getRuns();
        List<CTHyperlink> hyperlinkList = paragraph.getCTP().getHyperlinkList();

        List<DCRun> children = new ArrayList<>();
        for (XWPFRun run : runs) {
            DCRun dcRun = parserRun(run);
            dcRun.setParent(dcp);
            children.add(dcRun);
        }
        //解析超链接
        if(CollUtil.isNotEmpty(hyperlinkList)){
            //挨个处理超连接
            for (CTHyperlink ctHyperlink : hyperlinkList) {
                //获取超链接关联的文本信息
                List<String> linkTexts = new ArrayList<>();
                //DOTO  获取连接地址
                String id = ctHyperlink.getId();
                XWPFHyperlink xwpfHyperlink = paragraph.getDocument().getHyperlinkByID(id);
                String url = xwpfHyperlink.getURL();
                for (CTR ctr : ctHyperlink.getRList()) {
                    for (CTText ctText : ctr.getTList()) {
                        linkTexts.add(ctText.getStringValue());
                    }
                }
                //从run集合中查找能匹配的集合
                List<DCRun> dcRuns = hyperlinkRuns(linkTexts, children);
                //如果找到了 则将这些run用一个LinkRun包起来,并用linkRun替换dcRuns的位置
                if(dcRuns!=null){
                    DCLinkRun linkRun = new DCLinkRun();
                    linkRun.setHref(url);
                    linkRun.setChildren(new ArrayList<>(dcRuns));
                    int i = children.indexOf(dcRuns.get(0));
                    children.removeAll(dcRuns);
                    children.add(i,linkRun);
                }
            }
        }
        dcp.setChildren(new ArrayList<>(children));
        return dcp;
    }

    /**
     * 通过连接的run的文本集合来匹配DCRun子集
     * @param texts
     * @param allRun
     * @return
     */
    private List<DCRun> hyperlinkRuns(List<String> texts,List<DCRun> allRun){
        for (int i=0; i < allRun.size()-texts.size()+1; i++) {
            for(int j=0;j<texts.size();j++){
                if(texts.get(j).equals(allRun.get(i+j).getText())){
                    //如果全匹配上了，该子串则为超链接的内容
                    if(j == texts.size()-1){
                        return allRun.subList(i,i+j+1);
                    }
                }else{
                    break;
                }
            }
        }
        return null;
    }

    /**
     * run节点解析
     * @param run
     * @return
     */
    private DCRun parserRun(XWPFRun run){
        DCRun dcr = new DCRun();
        //设置样式
        DCRunStyle dcrs = new DCRunStyle(documentType);
        dcr.setStyle(dcrs);
        CTR ctr = run.getCTR();
        if(ctr.isSetRPr()){
            CTRPr rPr = ctr.getRPr();
            //加粗
            if(CollUtil.isNotEmpty(rPr.getBList())
                    &&CollUtil.isNotEmpty(rPr.getBCsList())){
                dcrs.setBold(true);
            }
            //下划线
            if(CollUtil.isNotEmpty(rPr.getUList())){
                dcrs.setUnderline(true);
            }
            //中划线
            if(CollUtil.isNotEmpty(rPr.getStrikeList())){
                dcrs.setLineThrough(true);
            }
            //字体
            if(CollUtil.isNotEmpty(rPr.getRFontsList())){
                CTFonts ctFonts = rPr.getRFontsArray(0);
                dcrs.setFontFamily(ctFonts.getAscii());
            }
            //字体大小
            if(CollUtil.isNotEmpty(rPr.getSzCsList())){
                CTHpsMeasure measure = rPr.getSzCsArray(0);
                dcrs.setFontSize(measure.getVal().toString());
            }
            //字体颜色
            if(CollUtil.isNotEmpty(rPr.getColorList())){
                CTColor color = rPr.getColorArray(0);
                dcrs.setColor(new String(HexUtil.encodeHex((byte[]) color.getVal())));
            }
            //背景色
            if(CollUtil.isNotEmpty(rPr.getShdList()) && rPr.getShdArray(0).isSetFill()){
                CTShd shd = rPr.getShdArray(0);
                dcrs.setBgColor(new String(HexUtil.encodeHex((byte[]) shd.getFill())));
            }
            //外边框
            if(CollUtil.isNotEmpty(rPr.getBdrList())){
                dcrs.setBorder(true);
            }
            //斜体
            if(CollUtil.isNotEmpty(rPr.getIList())){
                dcrs.setItalic(true);
            }
            //上标和下标
            if(CollUtil.isNotEmpty(rPr.getVertAlignList())){
                CTVerticalAlignRun alignRun = rPr.getVertAlignArray(0);
                if(alignRun.getVal() == STVerticalAlignRun.SUPERSCRIPT){
                    dcrs.setSuperscript(true);
                }else if (alignRun.getVal() == STVerticalAlignRun.SUBSCRIPT){
                    dcrs.setSubscript(true);
                }
            }
        }
        //设置文本
        if(CollUtil.isNotEmpty(ctr.getTList())){
            StringBuilder stb = new StringBuilder();
            for (CTText ctText : ctr.getTList()) {
                stb.append(ctText.getStringValue());
            }
            dcr.setText(stb.toString());
        }
        //图片
        if(CollUtil.isNotEmpty(ctr.getDrawingList())){
            CTDrawing drawing = ctr.getDrawingArray(0);
            CTGraphicalObject graphic=null;
            if(CollUtil.isNotEmpty(drawing.getInlineList())){
                CTInline inline = drawing.getInlineArray(0);
                graphic = inline.getGraphic();
            }else if(CollUtil.isNotEmpty(drawing.getAnchorList())) {
                CTAnchor anchor = drawing.getAnchorArray(0);
                graphic = anchor.getGraphic();
            }
            if(graphic!=null && graphic.getGraphicData()!=null){
                if(graphic.getGraphicData() instanceof  CTGraphicalObjectDataImpl){
                    CTGraphicalObjectDataImpl graphicData = (CTGraphicalObjectDataImpl) graphic.getGraphicData();
                    String xml = graphicData.toString();
                    XWPFPictureData pictureData=null;
                    if(xml.contains("r:embed=\"")){
                        xml = xml.substring(xml.indexOf("r:embed=\"")+9);
                        xml = xml.substring(0,xml.indexOf("\""));
                        pictureData = run.getDocument().getPictureDataByID(xml);
                    }
                    if(pictureData!=null){
                      DCPic pic = new DCPic();
                        try {
                            pic.setUrl(ImgUtil.storeTempImg(pictureData.getData(),pictureData.getFileName()));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        dcr.setPicData(pic);
                    }
                }
            }
        }
        return dcr;
    }
    /**
     * 识别段落类型
     * @param paragraph
     * @return
     */
    private DCParagraphType getType(XWPFParagraph paragraph){
        CTP ctp = paragraph.getCTP();
        XWPFDocument document = paragraph.getDocument();
        if(ctp.isSetPPr()){
            CTPPr pPr = ctp.getPPr();
            if(pPr.isSetPStyle()){
                XWPFStyle style = document.getStyles().getStyle(pPr.getPStyle().getVal());
                CTStyle ctStyle = style.getCTStyle();
                if(style.getType() == STStyleType.PARAGRAPH && ctStyle.isSetPPr() && ctStyle.getPPr().isSetOutlineLvl()){
                    return DCParagraphType.HEADER;
                }
            }else if (pPr.isSetNumPr()){
                CTNumPr numPr = pPr.getNumPr();
                CTDecimalNumber numId = numPr.getNumId();
                XWPFNumbering numbering = document.getNumbering();
                XWPFNum num = numbering.getNum(numId.getVal());
                XWPFAbstractNum abstractNum = numbering.getAbstractNum(num.getCTNum().getAbstractNumId().getVal());
                CTAbstractNum ctAbstractNum = abstractNum.getCTAbstractNum();
                CTLvl ctLvl = ctAbstractNum.getLvlArray(0);
                STNumberFormat.Enum val = ctLvl.getNumFmt().getVal();
                if(val == STNumberFormat.DECIMAL){
                    return DCParagraphType.ORDER_LIST;
                }else if(val == STNumberFormat.BULLET){
                    return DCParagraphType.UNORDER_LIST;
                }
            }
        }
        return DCParagraphType.NORMAL;
    }


}
